﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using PropertyExpression.Common;
using PropertyExpression.ControllableQuery.Test.Queries.T4;
using PropertyExpression.ControllableQuery.Test.Queries.StringBuilding;
using PropertyExpression.ControllableQuery.Toolkit;
using ModelPublicationSearchQuery = PropertyExpression.ControllableQuery.Test.Queries.T4.ModelPublicationSearchQuery;


namespace PropertyExpression.ControllableQuery.Test.QueryCheck
{
    [TestFixture]
    public class MainTest
    {
        //TODO: uncoment and delete
        //[Test]
        //public void ModelPublicationSearchQuery()
        //{
        //    CheckQueries(
        //        ConnectionString,
        //        queryContext => new ModelPublicationSearchQuery(
        //                            queryContext.CreateStringParam().AsOptionalValue(),
        //                            OptionalValue.Nothing<IParam<string>>(),
        //                            OptionalValue.Nothing<IParam<string>>()
        //                            ),
        //        queryContext => new ModelPublicationSearchQuery(
        //                            queryContext.CreateStringParam().AsOptionalValue(),
        //                            queryContext.CreateStringParam().AsOptionalValue(),
        //                            queryContext.CreateStringParam().AsOptionalValue()
        //                            ),
        //        queryContext => new ModelPublicationSearchQuery(
        //                            OptionalValue.Nothing<IParam<string>>(),
        //                            OptionalValue.Nothing<IParam<string>>(),
        //                            OptionalValue.Nothing<IParam<string>>()
        //                            )
        //        );
        //}

        //TODO: uncoment and delete
        //[Test]
        //public void PartLinkQuery()
        //{
        //    CheckQueries(
        //        ConnectionString,
        //        queryContext => new PartLinkQuery(
        //                            queryContext.CreateGuidParam(),
        //                            queryContext.CreateGuidParam(),
        //                            queryContext.CreateGuidParam(),
        //                            new WithInnerPartLinkSwitcher(
        //                                true,
        //                                queryContext.CreateStringParam(),
        //                                queryContext.CreateGuidParam())
        //                            )
        //        );
        //}

        //TODO: uncoment and delete
        //[Test]
        //public void TableValuedParameterQuery()
        //{
        //    CheckQueries(
        //        ConnectionString,
        //        queryContext => new TableValuedParameterQuery(
        //                            queryContext.CreateTableValuedParam<IPublicationCodeType>()
        //                            )
        //        );
        //}

        [Test]
        public void StringBuildingModelPublicationSearchQuery()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                    queryContext.CreateStringParam().AsOption(),
                                    Option.Nothing<IParam<string>>(),
                                    Option.Nothing<IParam<string>>()
                                    ),
                queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                    queryContext.CreateStringParam().AsOption(),
                                    queryContext.CreateStringParam().AsOption(),
                                    queryContext.CreateStringParam().AsOption()
                                    ),
                queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                    Option.Nothing<IParam<string>>(),
                                    Option.Nothing<IParam<string>>(),
                                    Option.Nothing<IParam<string>>()
                                    )
                );
        }

        [Test]
        public void StringBuildingModelPublicationQuery()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new ModelPublicationQuery(
                                    queryContext.CreateStringParam(),
                                    queryContext.CreateStringParam()
                                    )
                );
        }

        [Test]
        public void StringBuildingModelPublicationQuery2()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new ModelPublicationQuery2(
                                    queryContext.CreateStringParam(),
                                    queryContext.CreateStringParam()
                                    )
                );
        }

        [Test]
        public void PublicationTypeInsertQuery()
        {
            CheckNonQueries(
                ConnectionString,
                queryContext => new PublicationTypeInsertQuery(
                                    queryContext.CreateGuidParam(),
                                    queryContext.CreateIntParam(),
                                    queryContext.CreateNVarCharParam<L4000>(),
                                    queryContext.CreateDecimalParam<P18, S7>(),
                                    queryContext.CreateMoneyParam()
                                    )
                );
        }

        [Test]
        public void PublicationTypeUpdateQuery()
        {
            CheckNonQueries(
                ConnectionString,
                queryContext => new PublicationTypeUpdateQuery(
                                    queryContext.CreateGuidParam(),
                                    queryContext.CreateOptionalNVarCharParam<L4000>()
                                    )
                );
        }

        [Test]
        public void PublicationTypeSelectQuery()
        {
            CheckQueries(
                ConnectionString,
                queryContext => new PublicationTypeSelectQuery(queryContext.CreateGuidParam())
                );
        }

        [Test]
        public void PublicationTypeDeleteQuery()
        {
            CheckNonQueries(
                ConnectionString,
                queryContext => new PublicationTypeDeleteQuery(
                                    queryContext.CreateGuidParam()
                                    )
                );
        }

        [Test]
        public void ModelPublicationQuery()
        {
            CheckQueries(
                ConnectionString,
                queryContext => Query.ModelPublicationQuery(
                    queryContext.CreateStringParam(),
                    queryContext.CreateStringParam())
                );
        }

        private static void CheckQueries<T>(string connectionString, params Func<SqlCommand, IQuery<T>>[] queryFuncs)
        {
            QueryChecker.CheckQueries(connectionString, QueryResultPropertyTypeChecker, queryFuncs);
        }

        public static void CheckNonQueries(string connectionString, params Func<SqlCommand, INonQuery>[] queryFuncs)
        {
            QueryChecker.CheckNonQueries(connectionString, queryFuncs);
        }

        [Test]
        public void CheckAllQueries()
        {
            QueryChecker.CheckAllQueriesInAvailableAssemblies(
                ConnectionString,
                QueryResultPropertyTypeChecker,
                ParamCreator,
                QueryTypePredicate,
                MethodInfoPredicate,
                ChoiceInvocationGetter
                );
        }

        public static Func<MemberInfo, bool> MethodInfoPredicate
        {
            get { return memberInfo => true; }
        }

        public static ChoiceInvocationGetter ChoiceInvocationGetter
        {
            get { return delegate { throw new ApplicationException(); }; }
        }

        public static Func<Type, bool> QueryTypePredicate
        {
            get { return type => true; }
        }

        public static ParamCreator ParamCreator
        {
            get
            {
                return (type, queryContext) =>
                           {
                               var paramType = type.GetGenericArguments().Single();
                               if (typeof(string).Equals(paramType))
                               {
                                   return queryContext.CreateStringParam();
                               }
                               else if (typeof(IOption<string>).Equals(paramType))
                               {
                                   return queryContext.CreateOptionalStringParam();
                               }
                               else if (typeof(Guid).Equals(paramType))
                               {
                                   return queryContext.CreateGuidParam();
                               }
                               else if (typeof(IOption<Guid>).Equals(paramType))
                               {
                                   return queryContext.CreateOptionalGuidParam();
                               }
                               else if (typeof(int).Equals(paramType))
                               {
                                   return queryContext.CreateIntParam();
                               }
                               else if (typeof(IOption<int>).Equals(paramType))
                               {
                                   return queryContext.CreateOptionalIntParam();
                               }
                               else if (typeof(Money).Equals(paramType))
                               {
                                   return queryContext.CreateMoneyParam();
                               }
                               else if (paramType.IsGenericTypeAndEquals(typeof(ITableValued<>)))
                               {
                                   return createTableValuedParamMethodInfo.MakeGenericMethod(
                                       paramType.GetGenericArguments())
                                       .Invoke(null, new object[] { queryContext });
                               }
                               else if (paramType.IsGenericTypeAndEquals(typeof(NVarChar<>)))
                               {
                                   return createNVarCharParamMethodInfo.MakeGenericMethod(
                                       paramType.GetGenericArguments())
                                       .Invoke(null, new object[] { queryContext });
                               }
                               else if (paramType.IsGenericTypeAndEquals(typeof(IOption<>))
                                   && paramType.GetGenericArguments().Single().IsGenericTypeAndEquals(typeof(NVarChar<>)))
                               {
                                   return createOptionalNVarCharParamMethodInfo.MakeGenericMethod(
                                       paramType.GetGenericArguments().Single().GetGenericArguments())
                                       .Invoke(null, new object[] { queryContext });
                               }
                               else if (paramType.IsGenericTypeAndEquals(typeof(Decimal<,>)))
                               {
                                   return createDecimalParamMethodInfo.MakeGenericMethod(
                                       paramType.GetGenericArguments())
                                       .Invoke(null, new object[] { queryContext });
                               }
                               else
                               {
                                   throw new InvalidOperationException();
                               }
                           };
            }
        }

        private static readonly MethodInfo createDecimalParamMethodInfo =
            Toolkit.Util.GetMethodInfo<Func<SqlCommand, IParam<Decimal<P18, S7>>>>(_ => _.CreateDecimalParam<P18, S7>()).GetGenericMethodDefinition();

        private static readonly MethodInfo createOptionalNVarCharParamMethodInfo =
            Toolkit.Util.GetMethodInfo<Func<SqlCommand, IParam<IOption<NVarChar<L4000>>>>>(_ => _.CreateOptionalNVarCharParam<L4000>()).GetGenericMethodDefinition();

        private static readonly MethodInfo createNVarCharParamMethodInfo =
            Toolkit.Util.GetMethodInfo<Func<SqlCommand, IParam<NVarChar<L4000>>>>(_ => _.CreateNVarCharParam<L4000>()).GetGenericMethodDefinition();

        private static readonly MethodInfo createTableValuedParamMethodInfo =
            Toolkit.Util.GetMethodInfo<Func<SqlCommand, IParam<ITableValued<object>>>>(_ => _.CreateTableValuedParam<object>()).GetGenericMethodDefinition();

        public static QueryResultPropertyTypeChecker QueryResultPropertyTypeChecker
        {
            get
            {
                return (propertyInfo, dataRow, qLocation) =>
                           {
                               if (dataRow.GetDataTypeName().CompareSqlTypes("nvarchar"))
                               {
                                   CheckNVarCharPropertyType(propertyInfo, dataRow, qLocation);
                               }
                               else if (dataRow.GetDataTypeName().CompareSqlTypes("decimal"))
                               {
                                   CheckDecimalPropertyType(propertyInfo, dataRow, qLocation);
                               }
                               else if (dataRow.GetDataTypeName().CompareSqlTypes("money"))
                               {
                                   CheckPropertyType<Money>(propertyInfo, dataRow, qLocation);
                               }
                               else if (dataRow.GetDataTypeName().CompareSqlTypes("uniqueidentifier"))
                               {
                                   CheckPropertyType<Guid>(propertyInfo, dataRow, qLocation);
                               }
                               else if (dataRow.GetDataTypeName().CompareSqlTypes("int"))
                               {
                                   CheckPropertyType<int>(propertyInfo, dataRow, qLocation);
                               }
                               else
                               {
                                   throw new InvalidOperationException();
                               }
                           };
            }
        }

        private static void CheckDecimalPropertyType(PropertyInfo propertyInfo, DataRow dataRow, QLocation qLocation)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericTypeAndEquals(typeof (Decimal<,>))
                && dataRow.GetAllowDbNull() == false
                && propertyType.GetGenericArguments()[0].GetPrecision() == dataRow.GetNumericPrecision()
                && propertyType.GetGenericArguments()[1].GetScale() == dataRow.GetNumericScale())
            {
                //no-op
            }
            else if (propertyType.IsGenericTypeAndEquals(typeof (IOption<>))
                     && propertyType.GetGenericArguments().Single().IsGenericTypeAndEquals(typeof (Decimal<,>))
                     && dataRow.GetAllowDbNull() == true
                     && propertyType.GetGenericArguments().Single().GetGenericArguments()[0].GetPrecision() == dataRow.GetNumericPrecision()
                     && propertyType.GetGenericArguments().Single().GetGenericArguments()[1].GetScale() == dataRow.GetNumericScale())
            {
                //no-op
            }
            else
            {
                throw new QueryCheckException(
                    string.Format(
                        @"The type of query result property was incorrect ColumnName = '{0}'. Expected SQL Server Database Engine type DataType = '{1}', Precision = '{5}', Scale = '{6}', AllowDBNull = '{2}' but was .NET Framework type '{3}'.{4}",
                        dataRow.GetColumnName(),
                        dataRow.GetDataTypeName(),
                        dataRow.GetAllowDbNull(),
                        propertyType,
                        qLocation.Display(),
                        dataRow.GetNumericPrecision(),
                        dataRow.GetNumericScale()));

            }
            Console.WriteLine(@"{0} {1} '{4}' '{5}' '{2}' {3}",
                              dataRow.GetColumnName(),
                              dataRow.GetDataType(),
                              dataRow.GetAllowDbNull(),
                              propertyType,
                              dataRow.GetNumericPrecision(),
                              dataRow.GetNumericScale());
        }

        private static void CheckNVarCharPropertyType(PropertyInfo propertyInfo, DataRow dataRow, QLocation qLocation)
        {
            var propertyType = propertyInfo.PropertyType;
            if (propertyType.IsGenericTypeAndEquals(typeof(NVarChar<>))
                && dataRow.GetAllowDbNull() == false
                && propertyType.GetGenericArguments().Single().GetLength() == dataRow.GetColumnSize())
            {
                //no-op
            }
            else
            {
                if (propertyType.IsGenericTypeAndEquals(typeof(IOption<>))
                    && propertyType.GetGenericArguments().Single().IsGenericTypeAndEquals(typeof(NVarChar<>))
                    && dataRow.GetAllowDbNull() == true
                    && propertyType.GetGenericArguments().Single().GetGenericArguments().Single().GetLength() == dataRow.GetColumnSize())
                {
                    //no-op
                }
                else
                {
                    throw new QueryCheckException(
                        string.Format(
                            @"The type of query result property was incorrect ColumnName = '{0}'. Expected SQL Server Database Engine type DataType = '{1}', ColumnSize = '{5}', AllowDBNull = '{2}' but was .NET Framework type '{3}'.{4}",
                            dataRow.GetColumnName(),
                            dataRow.GetDataTypeName(),
                            dataRow.GetAllowDbNull(),
                            propertyType,
                            qLocation.Display(),
                            dataRow.GetColumnSize()));
                }
            }
            Console.WriteLine(@"{0} {1} '{4}' '{2}' {3}",
                              dataRow.GetColumnName(),
                              dataRow.GetDataType(),
                              dataRow.GetAllowDbNull(),
                              propertyType,
                              dataRow.GetColumnSize());
        }

        private static void CheckPropertyType<T>(PropertyInfo propertyInfo, DataRow dataRow, QLocation qLocation)
        {
            var propertyType = propertyInfo.PropertyType;
            if (typeof(T).Equals(propertyType) && dataRow.GetAllowDbNull() == false)
            {
                //no-op
            }
            else if (typeof(IOption<T>).Equals(propertyType) && dataRow.GetAllowDbNull() == true)
            {
                //no-op
            }
            else
            {
                throw new QueryCheckException(
                    string.Format(
                        @"The type of query result property was incorrect ColumnName = '{0}'. Expected SQL Server Database Engine type DataType = '{1}', AllowDBNull = '{2}' but was .NET Framework type '{3}'.{4}",
                        dataRow.GetColumnName(),
                        dataRow.GetDataTypeName(),
                        dataRow.GetAllowDbNull(),
                        propertyType,
                        qLocation.Display()));
            }
        }

        private static string ConnectionString
        {
            get { return Test.MainTest.ConnectionString; }
        }
    }
}